#!/usr/bin/python
# -*- coding: utf-8 -*-
"""A parser for ASN1 object encoded using BER

The doc string just sketches the names of objects in the module.
Consult the documentation for more details.

Burton S. Kaliski Jr. wrote a helpful introduction to ASN.1 and the
BER encoding titled 'A Layman's Guide to a Subset of ASN.1, BER, and
DER.'  It is available from http://www.rsasecurity.com/rsalabs/pkcs/.
The text version is available at
    ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc.
    
functions:
    parse(buf: string) -> ASN1Object
    display(obj: ASN1Object)
    parseCfg(path) -> {oid:name}

classes:
    ASN1Object
    plus subclasses for each asn.1 type, e.q. Sequence, Set, etc.

constants:
    INTEGER, BIT_STRING, OCTET_STRING, NULL, OBJECT_IDENTIFIER,
    SEQUENCE, SET, PrintableString, T61String, IA5String, UTCTIME,
    BOOLEAN 

The following objects are not part of the user-visible API:
Displayer
ASN1Parser
unparseLengthXXX functions
"""

import string
import struct
import operator
import types
import UserList
import time
from datetime import datetime
try:
    import cStringIO
    StringIO = cStringIO.StringIO
except ImportError:
    import StringIO
    StringIO = StringIO.StringIO

class EOFError(IOError):
    pass

__oids = [ # http://msdn.microsoft.com/en-us/library/aa379070(VS.85).aspx
    #XCN_CRYPT_HASH_ALG_OID_GROUP_ID
    #Hashing algorithm group. This includes the following OIDs:
    ('OIWSEC_sha',                      '1.3.14.3.2.18'),
    ('SHA1',                            '1.3.14.3.2.26'),
    ('MD2',                             '1.2.840.113549.2.2'),
    ('MD4',                             '1.2.840.113549.2.4'),
    ('MD5',                             '1.2.840.113549.2.5'),

    #XCN_CRYPT_ENCRYPT_ALG_OID_GROUP_ID
    #Symmetric encryption algorithm group. This includes the following OIDs:
    ('NIST_AES128_CBC',                 '2.16.840.1.101.3.4.1.2'),
    ('NIST_AES192_CBC',                 '2.16.840.1.101.3.4.1.22'),
    ('NIST_AES256_CBC',                 '2.16.840.1.101.3.4.1.42'),
    ('NIST_AES128_WRAP',                '2.16.840.1.101.3.4.1.5'),
    ('NIST_AES192_WRAP',                '2.16.840.1.101.3.4.1.25'),
    ('NIST_AES256_WRAP',                '2.16.840.1.101.3.4.1.45'),
    ('OIWSEC_desCBC',                   '1.3.14.3.2.7'),
    ('RSA_DES_EDE3_CBC',                '1.2.840.113549.3.7'),
    ('RSA_RC2CBC',                      '1.2.840.113549.3.2'),
    ('RSA_RC4',                         '1.2.840.113549.3.4'),
    ('RSA_SMIMEalgCMS3DESwrap',         '1.2.840.113549.1.9.16.3.6'),
    ('RSA_SMIMEalgCMSRC2wrap',          '1.2.840.113549.1.9.16.3.7'),

    #XCN_CRYPT_PUBKEY_ALG_OID_GROUP_ID
    #Asymmetric encryption algorithm group. This includes the following OIDs:
    ('ANSI_X942_DH',                    '1.2.840.10046.2.1'),
    ('DH_SINGLE_PASS_STDDH_SHA1_KDF',   '1.3.133.16.840.63.0.2'),
    ('ECC_CURVE_P256',                  '1.2.840.10045.3.1.7'),
    ('ECC_CURVE_P384',                  '1.3.132.0.34'),
    ('ECC_CURVE_P521',                  '1.3.132.0.35'),
    ('ECC_PUBLIC_KEY',                  '1.2.840.10045.2.1'),
    ('INFOSEC_mosaicKMandUpdSig',       '2.16.840.1.101.2.1.1.20'),
    ('OIWSEC_dsa',                      '1.3.14.3.2.12'),
    ('OIWSEC_rsaXchg',                  '1.3.14.3.2.22'),
    ('PKIX_NO_SIGNATURE',               '1.3.6.1.5.5.7.6.2'),
    ('RSA_DH',                          '1.2.840.113549.1.3.1'),
    ('RSA',                         '1.2.840.113549.1.1.1'),
    ('RSA_SMIMEalgESDH',                '1.2.840.113549.1.9.16.3.5'),
    ('RSAES_OAEP',                      '1.2.840.113549.1.1.7'),
    ('X957_DSA',                        '1.2.840.10040.4.1'),

    #XCN_CRYPT_SIGN_ALG_OID_GROUP_ID
    #Signing algorithm group. This includes the following OIDs:
    ('ECDSA_SHA1',                      '1.2.840.10045.4.1'),
    ('ECDSA_SHA256',                    '1.2.840.10045.4.3.2'),
    ('ECDSA_SHA384',                    '1.2.840.10045.4.3.3'),
    ('ECDSA_SHA512',                    '1.2.840.10045.4.3.4'),
    ('ECDSA_SPECIFIED',                 '1.2.840.10045.4.3'),
    ('INFOSEC_mosaicUpdatedSig',        '2.16.840.1.101.2.1.1.19'),
    ('NIST_sha256',                     '2.16.840.1.101.3.4.2.1'),
    ('NIST_sha384',                     '2.16.840.1.101.3.4.2.2'),
    ('NIST_sha512',                     '2.16.840.1.101.3.4.2.3'),
    ('OIWDIR_md2RSA',                   '1.3.14.7.2.3.1'),
    ('OIWSEC_dsaSHA1',                  '1.3.14.3.2.27'),
    ('OIWSEC_md4RSA',                   '1.3.14.3.2.2'),
    ('OIWSEC_md4RSA2',                  '1.3.14.3.2.4'),
    ('OIWSEC_md5RSA',                   '1.3.14.3.2.3'),
    ('OIWSEC_sha1RSASign',              '1.3.14.3.2.29'),
    ('OIWSEC_shaDSA',                   '1.3.14.3.2.13'),
    ('OIWSEC_shaRSA',                   '1.3.14.3.2.15'),
    ('MD2RSA',                      '1.2.840.113549.1.1.2'),
    ('MD4RSA',                      '1.2.840.113549.1.1.3'),
    ('MD5RSA',                      '1.2.840.113549.1.1.4'),
    ('SHA1RSA',                     '1.2.840.113549.1.1.5'),
    ('SHA256RSA',                   '1.2.840.113549.1.1.11'),
    ('SHA384RSA',                   '1.2.840.113549.1.1.12'),
    ('SHA512RSA',                   '1.2.840.113549.1.1.13'),
    ('SSA_PSS',                     '1.2.840.113549.1.1.10'),
    ('X957_SHA1DSA',                    '1.2.840.10040.4.3'),

    #XCN_CRYPT_RDN_ATTR_OID_GROUP_ID
    #Relative distinguished name (RDN) group. This includes the following OIDs:
    ('CN',                              '2.5.4.3'), #Common Name
    ('L',                               '2.5.4.7'), #LOCALITY_NAME
    ('O',                               '2.5.4.10'), #Organization
    ('OU',                              '2.5.4.11'), #Organizational Unit
    ('RSA_emailAddr',                   '1.2.840.113549.1.9.1'),
    ('C',                               '2.5.4.6'), #Country
    ('S',                               '2.5.4.8'), #STATE_OR_PROVINCE_NAME
    ('STREET_ADDRESS',                  '2.5.4.9'),
    ('TITLE',                           '2.5.4.12'),
    ('GIVEN_NAME',                      '2.5.4.42'),
    ('INITIALS',                        '2.5.4.43'),
    ('SUR_NAME',                        '2.5.4.4'),
    ('DEVICE_SERIAL_NUMBER',            '2.5.4.5'),
    ('DOMAIN_COMPONEN',                 '0.9.2342.19200300.100.1.25'),
    ('DESCRIPTION',                     '2.5.4.13'),
    ('POSTAL_CODE',                     '2.5.4.17'),
    ('POST_OFFICE_BOX',                 '2.5.4.18'),
    ('TELEPHONE_NUMBER',                '2.5.4.20'),
    ('X21_ADDRESS',                     '2.5.4.24'),
    ('DN_QUALIFIER',                    '2.5.4.46'),

    #XCN_CRYPT_EXT_OR_ATTR_OID_GROUP_ID
    #Extension and attribute group. This includes the following OIDs:
    ('CTL',                             '1.3.6.1.4.1.311.10.1'),
    ('CMC_ADD_ATTRIBUTES',              '1.3.6.1.4.1.311.10.10.1'),
    ('NEXT_UPDATE_LOCATION ',           '1.3.6.1.4.1.311.10.2'),
    ('SERIALIZED',                      '1.3.6.1.4.1.311.10.3.3.1'),
    ('YESNO_TRUST_ATTR',                '1.3.6.1.4.1.311.10.4.1'),
    ('CROSS_CERT_DIST_POINTS',          '1.3.6.1.4.1.311.10.9.1'),
    ('ENROLLMENT_NAME_VALUE_PAIR',      '1.3.6.1.4.1.311.13.2.1'),
    ('ENROLLMENT_CSP_PROVIDER',         '1.3.6.1.4.1.311.13.2.2'),
    ('OS_VERSION',                      '1.3.6.1.4.1.311.13.2.3'),
    ('CERT_EXTENSIONS',                 '1.3.6.1.4.1.311.2.1.14'),
    ('ENROLL_CERTTYPE_EXTENSION',       '1.3.6.1.4.1.311.20.2'),
    ('NT_PRINCIPAL_NAME',               '1.3.6.1.4.1.311.20.2.3'),
    ('CERT_MANIFOLD',                   '1.3.6.1.4.1.311.20.3'),
    ('CERTSRV_CA_VERSION',              '1.3.6.1.4.1.311.21.1'),
    ('APPLICATION_CERT_POLICIES',       '1.3.6.1.4.1.311.21.10'),
    ('APPLICATION_POLICY_MAPPINGS',     '1.3.6.1.4.1.311.21.11'),
    ('APPLICATION_POLICY_CONSTRAINTS',  '1.3.6.1.4.1.311.21.12'),
    ('ARCHIVED_KEY_ATTR',               '1.3.6.1.4.1.311.21.13'),
    ('CRL_SELF_CDP',                    '1.3.6.1.4.1.311.21.14'),
    ('REQUIRE_CERT_CHAIN_POLICY',       '1.3.6.1.4.1.311.21.15'),
    ('ARCHIVED_KEY_CERT_HASH',          '1.3.6.1.4.1.311.21.16'),
    ('CERTSRV_PREVIOUS_CERT_HASH',      '1.3.6.1.4.1.311.21.2'),
    ('REQUEST_CLIENT_INFO',             '1.3.6.1.4.1.311.21.20'),
    ('CERTSRV_CROSSCA_VERSION',         '1.3.6.1.4.1.311.21.22'),
    ('CRL_VIRTUAL_BASE',                '1.3.6.1.4.1.311.21.3'),
    ('CRL_NEXT_PUBLISH',                '1.3.6.1.4.1.311.21.4'),
    ('KP_CA_EXCHANGE',                  '1.3.6.1.4.1.311.21.5'),
    ('KP_KEY_RECOVERY_AGENT',           '1.3.6.1.4.1.311.21.6'),
    ('KP_KEY_RECOVERY_AGENT',           '1.3.6.1.4.1.311.21.7'),
    ('ENTERPRISE_OID_ROOT',             '1.3.6.1.4.1.311.21.8'),
    ('RDN_DUMMY_SIGNER',                '1.3.6.1.4.1.311.21.9'),
    ('PRODUCT_UPDATE',                  '1.3.6.1.4.1.311.31.1'),
    ('AUTHORITY_INFO_ACCESS',           '1.3.6.1.5.5.7.1.1'),
    ('LOGOTYPE_EXT',                    '1.3.6.1.5.5.7.1.12'),
    ('BIOMETRIC_EXT',                   '1.3.6.1.5.5.7.1.2'),
    ('CT_PKI_DATA',                     '1.3.6.1.5.5.7.12.2'),
    ('CT_PKI_RESPONSE',                 '1.3.6.1.5.5.7.12.3'),
    ('PKIX_POLICY_QUALIFIER_CPS',       '1.3.6.1.5.5.7.2.1'),
    ('PKIX_POLICY_QUALIFIER_USERNOTICE','1.3.6.1.5.5.7.2.2'),
    ('PKIX_OCSP',                       '1.3.6.1.5.5.7.48.1'),
    ('PKIX_OCSP_NOCHECK',               '1.3.6.1.5.5.7.48.1.5'),
    ('PKIX_CA_ISSUERS',                 '1.3.6.1.5.5.7.48.2'),
    ('CMC',                             '1.3.6.1.5.5.7.7'),
    ('CMC_STATUS_INFO',                 '1.3.6.1.5.5.7.7.1'),
    ('CMC_GET_CERT',                    '1.3.6.1.5.5.7.7.15'),
    ('CMC_GET_CRL',                     '1.3.6.1.5.5.7.7.16'),
    ('CMC_REVOKE_REQUEST',              '1.3.6.1.5.5.7.7.17'),
    ('CMC_REG_INFO',                    '1.3.6.1.5.5.7.7.18'),
    ('CMC_QUERY_PENDING',               '1.3.6.1.5.5.7.7.21'),
    ('CMC_TRANSACTION_ID',              '1.3.6.1.5.5.7.7.5'),
    ('CMC_SENDER_NONCE',                '1.3.6.1.5.5.7.7.6'),
    ('CMC_RECIPIENT_NONCE',             '1.3.6.1.5.5.7.7.7'),
    ('CMC_ADD_EXTENSIONS',              '1.3.6.1.5.5.7.7.8'),
    ('AUTHORITY_KEY_IDENTIFIER',        '2.5.29.1'),
    ('BASIC_CONSTRAINTS',               '2.5.29.10'),
    ('SUBJECT_KEY_IDENTIFIER',          '2.5.29.14'),
    ('KEY_USAGE',                       '2.5.29.15'),
    ('PRIVATEKEY_USAGE_PERIOD',         '2.5.29.16'),
    ('SUBJECT_ALT_NAME2',               '2.5.29.17'),
    ('ISSUER_ALT_NAME2',                '2.5.29.18'),
    ('BASIC_CONSTRAINTS2',              '2.5.29.19'),
    ('KEY_ATTRIBUTES',                  '2.5.29.2'),
    ('CRL_NUMBER',                      '2.5.29.20'),
    ('CRL_REASON_CODE',                 '2.5.29.21'),
    ('DELTA_CRL_INDICATOR',             '2.5.29.27'),
    ('ISSUING_DIST_POINT',              '2.5.29.28'),
    ('NAME_CONSTRAINTS',                '2.5.29.30'),
    ('CRL_DIST_POINTS',                 '2.5.29.31'),
    ('CERT_POLICIES',                   '2.5.29.32'),
    ('POLICY_MAPPINGS',                 '2.5.29.33'),
    ('AUTHORITY_KEY_IDENTIFIER2',       '2.5.29.35'),
    ('POLICY_CONSTRAINTS',              '2.5.29.36'),
    ('ENHANCED_KEY_USAGE',              '2.5.29.37'),
    ('KEY_USAGE_RESTRICTION',           '2.5.29.4'),
    ('FRESHEST_CRL',                    '2.5.29.46'),
    ('LEGACY_POLICY_MAPPINGS',          '2.5.29.5'),
    ('SUBJECT_ALT_NAME',                '2.5.29.7'),
    ('ISSUER_ALT_NAME',                 '2.5.29.8'),
    ('TITLE',                           '2.5.4.12'),
    ('SUR_NAME',                        '2.5.4.4'),
    ('GIVEN_NAME',                      '2.5.4.42'),
    ('INITIALS',                        '2.5.4.43'),
    ('DEVICE_SERIAL_NUMBER',            '2.5.4.5'),
    ('STREET_ADDRESS',                  '2.5.4.9'),

    #XCN_CRYPT_ENHKEY_USAGE_OID_GROUP_ID
    #Enhanced key usage (EKU) extension group. This includes the following OIDs:
    ('PKIX_KP_SERVER_AUTH',             '1.3.6.1.5.5.7.3.1'),
    ('PKIX_KP_CLIENT_AUTH',             '1.3.6.1.5.5.7.3.2'),
    ('PKIX_KP_CODE_SIGNING',            '1.3.6.1.5.5.7.3.3'),
    ('PKIX_KP_EMAIL_PROTECTION',        '1.3.6.1.5.5.7.3.4'),
    ('PKIX_KP_TIMESTAMP_SIGNING',       '1.3.6.1.5.5.7.3.8'),
    ('KP_CTL_USAGE_SIGNING',            '1.3.6.1.4.1.311.10.3.1'),
    ('KP_TIME_STAMP_SIGNING',           '1.3.6.1.4.1.311.10.3.2'),
    ('PKIX_KP_IPSEC_END_SYSTEM',        '1.3.6.1.5.5.7.3.5'),
    ('PKIX_KP_IPSEC_TUNNEL',            '1.3.6.1.5.5.7.3.6'),
    ('PKIX_KP_IPSEC_USER',              '1.3.6.1.5.5.7.3.7'),
    ('KP_EFS',                          '1.3.6.1.4.1.311.10.3.4'),
    ('WHQL_CRYPTO',                     '1.3.6.1.4.1.311.10.3.5'),
    ('NT5_CRYPTO',                      '1.3.6.1.4.1.311.10.3.6'),
    ('OEM_WHQL_CRYPTO',                 '1.3.6.1.4.1.311.10.3.7'),
    ('EMBEDDED_NT_CRYPTO',              '1.3.6.1.4.1.311.10.3.8'),
    ('LICENSES',                        '1.3.6.1.4.1.311.10.6.1'),
    ('LICENSE_SERVER',                  '1.3.6.1.4.1.311.10.6.2'),
    ('KP_SMARTCARD_LOGON',              '1.3.6.1.4.1.311.20.2.2'),
    ('DRM',                             '1.3.6.1.4.1.311.10.5.1'),
    ('KP_QUALIFIED_SUBORDINATION',      '1.3.6.1.4.1.311.10.3.10'),
    ('KP_KEY_RECOVERY',                 '1.3.6.1.4.1.311.10.3.11'),
    ('KP_DOCUMENT_SIGNING',             '1.3.6.1.4.1.311.10.3.12'),
    ('IPSEC_KP_IKE_INTERMEDIATE',       '1.3.6.1.5.5.8.2.2'),
    ('EFS_RECOVERY',                    '1.3.6.1.4.1.311.10.3.4.1'),
    ('ROOT_LIST_SIGNER',                '1.3.6.1.4.1.311.10.3.9'),
    ('ANY_APPLICATION_POLICY',          '1.3.6.1.4.1.311.10.12.1'),
    ('DS_EMAIL_REPLICATION',            '1.3.6.1.4.1.311.21.19'),
    ('ENROLLMENT_AGENT',                '1.3.6.1.4.1.311.20.2.1'),
    ('KP_KEY_RECOVERY_AGENT',           '1.3.6.1.4.1.311.21.6'),
    ('KP_CA_EXCHANGE',                  '1.3.6.1.4.1.311.21.5'),
    ('KP_LIFETIME_SIGNING',             '1.3.6.1.4.1.311.10.3.13'),
    ('PKIX_KP_OCSP_SIGNING',            '1.3.6.1.5.5.7.3.9'),

    #XCN_CRYPT_POLICY_OID_GROUP_ID
    #Issuance policy group. This includes the following OIDs. The x.y.z portion of each OID represents a randomly generated numeric sequence that is unique for each forest.
    ('ANY_CERT_POLICY',                 '2.5.29.32.0'),
#        * Low Assurance (1.3.6.1.4.1.311.21.8.x.y.z.1.400)
#        * Medium Assurance (1.3.6.1.4.1.311.21.8.x.y.z.1.401)
#        * High Assurance (1.3.6.1.4.1.311.21.8.x.y.z.1.402)
    # algorthims
    ('PKCS', '1.2.840.113549.1'),
    ('RSA_HASH', '1.2.840.113549.2'),
    ('RSA_ENCRYPT', '1.2.840.113549.3'),
    # other
    ('SPC_INDIRECT_DATA', '1.3.6.1.4.1.311.2.1.4'),
    ('SPC_PE_IMAGE_DATA', '1.3.6.1.4.1.311.2.1.15'),
    ('SPC_SP_OPUS_INFO', '1.3.6.1.4.1.311.2.1.12'),
    ('Countersignature', '1.2.840.113549.1.9.6'),
    ('ContentType', '1.2.840.113549.1.9.3'),
    ('MessageDigest', '1.2.840.113549.1.9.4'),
    ('Netscape_certificate_type', '2.16.840.1.113730.1.1'),
    ('SignedData', '1.2.840.113549.1.7.2'),
    ('SigningTime', '1.2.840.113549.1.9.5'),
    ('Data', '1.2.840.113549.1.7.1'), #http://tools.ietf.org/html/rfc3369#section-4
    #spcStatementType (Microsoft code signing)
    ('SPC_STATEMENT_TYPE',           '1.3.6.1.4.1.311.2.1.11'), #http://support.microsoft.com/kb/287547
    ('SPC_INDIVIDUAL_SP_KEY_PURPOSE' ,    '1.3.6.1.4.1.311.2.1.21'),
    ('SPC_COMMERCIAL_SP_KEY_PURPOSE',     '1.3.6.1.4.1.311.2.1.22'),
    ('SPC_PE_IMAGE_PAGE_HASHES_V1', '1.3.6.1.4.1.311.2.3.1'), # V1
]
OIDS = dict(__oids+[(e[1], e[0]) for e in __oids])

CLASS_MASK = 0xC0
TYPE_MASK = 0x1F
class ClassType:
    Universal       = 0x00 #0 0
    Application     = 0x40 #0 1
    ContextSpecific = 0x80 #1 0
    Private         = 0xC0 #1 1

asn1_types = [
    ('ZERO',                        0x00),
    ('BOOLEAN',                     0x01),
    ('INTEGER',                     0x02),
    ('BIT_STRING',                  0x03),
    ('OCTET_STRING',                0x04),
    ('NULL',                        0x05),
    ('OBJECT_IDENTIFIER',           0x06),
    ('OBJECT_DESCRIPTOR',           0x07),
    ('EXTERNAL',                    0x08),
    ('REAL',                        0x09),
    ('ENUMERATED',                  0x0A),
    ('UTF8String',                  0x0C),
    ('SEQUENCE',                    0x10),
    ('SET',                         0x11),
    ('NumericString',               0x12),
    ('PrintableString',             0x13),
    ('T61String',                   0x14),
    ('IA5String',                   0x16),  # IA5 == ASCII
    ('UTCTIME',                     0x17),
    ('VisibleString',               0x1A),
    ('BMPString',                   0x1E),
 ]

ASN1_TYPES = dict([(e[1], e[0]) for e in asn1_types] + asn1_types)

class Displayer:
    def __init__(self, oids=None):
        if oids:
            self.oids = oids
    def __call__(self, obj, indent=0):
        try:
            if obj.atomic:
                if self.oids and isinstance(obj, OID) \
                   and self.oids.has_key(obj):
                    info = self.oids[obj]
                    if info.has_key('Warning'):
                        print " " * indent, "OID", info['Description'], \
                              "Warning"
                    else:
                        print " " * indent, "OID", info['Description']
                    return
                print " " * indent, str(obj)
            else:
                if isinstance(obj, Contextual):
                    print " " * indent, "[%d]"% obj.tag
                    display(obj.val, indent+1)
                else:
                    print " " * indent, obj.__class__.__name__, "{"
                    for elt in obj.val:
                        display(elt, indent+1)
                    print " " * indent, "}"
        except AttributeError:
            print " " * indent, `obj`
        
def display(obj, indent=0, padding='.'):
    # XXX doesn't work on constructed yet
    if isinstance(obj, ASN1Object):
        f = lambda x:len(x)==1 and '0%s' % x or x
        pad = '%6d: 0x%s  %5d: %s' % (obj.offset, f('%X' % obj.id), obj.length, padding * indent)
        if obj.atomic:
            print pad, obj
        else:
            if isinstance(obj, Contextual):
                if not isinstance(obj.val, list):
                    print pad, '[%d]' % obj.tag, 'CONTEXTUAL', "{", obj.val, "}"
                    return 
                else:
                    print pad, '[%d]' % obj.tag, 'CONTEXTUAL', "{"
            else:
                print pad, ASN1_TYPES[obj.tag], "{"
            for elt in obj.val:
                display(elt, indent+1, padding)
            print ' ' * (len(pad) - indent - 2) + ':', padding * indent, "}"
    else:
        print padding * indent, `obj`

class ASN1Object:
    atomic = 1

    def __init__(self, val):
        self.val = val

    # XXX need to make sure this really works everywhere; it's a late
    # addition.  it requires that all objects have a val that is a
    # list. 
    def __cmp__(self, other):
	if isinstance(other, ASN1Object):
	    return cmp(self.val, other.val)
	return -1

    def __repr__(self):
        return "%s:%s" % (ASN1_TYPES[self.tag], repr(self.val))

    def encode(self, io=None):
        if io is None:
            io = StringIO()
            ioFlag = 1
        else:
            ioFlag = 0
        try:
            encode = self._encode
        except AttributeError:
            raise AttributeError, \
                  ("%s instance does not implement _encode" %
                   self.__class__.__name__)
        else:
            encode(io)
        if ioFlag:
            return io.getvalue()

class Sequence(ASN1Object, UserList.UserList):
    atomic = 0
    def __init__(self, val=None):
        if not val:
            val = []
        self.val = self.data = val
            
    def __repr__(self):
        s = "SEQUENCE {" + repr(self.val)[1:-1] + "}"
        if hasattr(self, 'choose'):
            s = '[%d]' % self.choose + s
        return s
    
    def _encode(self, io):
        encObjs = []
        for elt in self.data:
            _type = type(elt)
            if _type == types.InstanceType:
                encObjs.append(elt.encode())
            elif _type == types.IntType or _type == types.LongType:
                encObjs.append(unparseInteger(elt))
            else:
                raise RuntimeError, "can't encode sequence containg %s" % _type
        io.write(unparseSequence(encObjs))

class Set(ASN1Object, UserList.UserList):
    atomic = 0
    def __init__(self):
        self.val = self.data = []
    def __repr__(self):
        s = "SET {" + repr(self.val) + "}"
        if hasattr(self, 'choose'):
            s = '[%d]' % self.choose + s
        return s

class UTCTime(ASN1Object):
    """Standard ASN.1 type for time expressed in GMT

    draft-ietf-pkix-ipki-part1-08.txt notes:
    For the purposes of this profile, UTCTime values shall be expressed
    Greenwich Mean Time (Zulu) and shall include seconds (i.e., times are
    YYMMDDHHMMSSZ), even where the number of seconds is zero.  Conforming
    systems shall interpret the year field (YY) as follows:

    Where YY is greater than or equal to 50, the year shall be inter-
    preted as 19YY; and

    Where YY is less than 50, the year shall be interpreted as 20YY.
    """

    def __init__(self, val=None):
        if type(val) == types.StringType:
            self.val = val
            self._val = None
        else:
            self.unparse(val)

    def __cmp__(self, other):
        return cmp(self.val, other.val)

    def _encode(self, io):
        io.write(chr(ASN1_TYPES['UTCTIME']) + unparseLength(len(self.val)) + self.val)
        
    def unparse(self, val):
        """Convert a Python time representation to UTC time.

        Argument must be in UTC.
        """
        # Python dates might be represented as seconds or time tuples.
        # I'll simply require that both times have the same repr.
        
        # UTC is easier to cope with because the user can make sure a
        # time tuple is in  UTC, but it would be a pain for me to do that.
        self._val = time.mktime(val)
        if type(val) != types.TupleType:
            try:
                val = time.gmtime(val)
            except TypeError:
                raise TypeError, "time must be seconds or time-tuple"
        yy = val[0]
        if yy >= 2000:
            yy = yy - 2000
            if yy >= 50:
                # man this is braind-dead
                raise ValueError, "can't handle data that far in future"
        elif yy < 2000:
            yy = yy - 1900
            if yy < 50:
                raise ValueError, "can't handle data that far in past"
        self.val = "%02d%02d%02d%02d%02d%02dZ" % (yy, val[1], val[2],
                                                  val[3], val[4], val[5])
        
    def _parse(self):
        if self._val:
            return self._val
        yy = string.atoi(self.val[:2])
        mm1 = string.atoi(self.val[2:4])
        dd = string.atoi(self.val[4:6])
        hh = string.atoi(self.val[6:8])
        mm2 = string.atoi(self.val[8:10])
        ss = string.atoi(self.val[10:12])
        assert self.val[-1] == 'Z'

        if yy >= 50:
            yy = 1900 + yy
        else:
            yy = 2000 + yy
        self._val = (yy, mm1, dd, hh, mm2, ss)
        return self._val
    
    @property
    def datetime(self):
        y, m, d, h, mon, s = self._parse()
#        d = datetime.utcfromtimestamp(y, m, d, h, mon, s)
        d = datetime(y, m, d, h, mon, s)
#        print d
        return d
        
class Contextual(ASN1Object, UserList.UserList):
    """
    """
    atomic = 0
    def __init__(self, constructed):
        self.constructed = constructed
        self.data = []
        
    def __repr__(self):
        return "[%d] Contextual %s {%s}" % (self.tag, 
                                            (self.constructed and 'constructed' or ''), 
                                            repr(self.val))
    
    def get_val(self):
        if self.constructed:
            return self.data
        else:
            return self.data[0]
    val = property(get_val)
    
#    def append(self, val):
#        self.data.append(val)

class Boolean(ASN1Object):
    def __nonzero__(self):
        if self.val:
            return 1
        else:
            return 0
        
    def __repr__(self):
        if self.val:
            return 'TRUE'
        else:
            return 'FALSE'
        
    def _encode(self, io):
        io.write(chr(BOOLEAN) + chr(1) + chr(self.val))

class Null(ASN1Object):
    tag = 0x05
    def __nonzero__(self):
        return 0
        
    def __repr__(self):
        return ASN1_TYPES[self.tag]
        
class Integer(ASN1Object):
    tag = 0x02
    def __repr__(self):
        return '%s:%s' % (ASN1_TYPES[self.tag], self.val)
    
    @property
    def str(self):
        return _hex_str(self.val)

class ASN1String(ASN1Object):
    def __init__(self, val):
        ASN1Object.__init__(self, val)
        self.str_val = val
    def __repr__(self):
        return '%s:%s' % (ASN1_TYPES[self.tag], self.str)
    
    @property
    def str(self):
        return _hex_str(self.str_val)

class EncapsulateString(ASN1String):
    def __init__(self, val):
        ASN1String.__init__(self, val)
        self._try_encapsulate(val)
    
    def _try_encapsulate(self, buf):
        try:
            asnparser = ASN1Parser(StringIO(buf))
            self.encapsulate = True
        except:
            self.encapsulate = False
            return 
        if asnparser.check_encapsulate(len(buf)):
            self.str_val = buf
            self.val = asnparser.parse()
        
    def __repr__(self):
        if isinstance(self.val, ASN1Object):
            return '%s, encapsulates:%s' % (ASN1_TYPES[self.tag], self.val)
        return '%s:%s' % (ASN1_TYPES[self.tag], self.str)
    
class BitString(EncapsulateString):
    tag = 0x03
    def __init__(self, val, buf=None):
        ASN1String.__init__(self, val)
#        if buf is not None:
#            self._try_encapsulate(buf)
    
class OctetString(EncapsulateString):
    """"""
    tag = 0x04
    
class PrintString(ASN1Object):
    """"""

class BMPString(ASN1Object):
    """2 byte unicode string"""
    def __init__(self, val):
        ASN1Object.__init__(self, val)
        self._convert()
    
    def _convert(self):
        bytes = map(ord, self.val)
        index = 0
        buf = u''
        while index < len(self.val):
            i = ord(self.val[index]) << 8 | ord(self.val[index+1])
            buf += unichr(i)
            index += 2
        self.val = buf
    
    def __unicode__(self):
        return self.val
    
class UTF8String(ASN1Object):
    """str encoding with utf-8"""
    def __init__(self, val):
        ASN1Object.__init__(self, val)
        self.val = self.val.decode('utf-8')
        
    def __unicode__(self):
        return self.val
            
class OID(ASN1Object):
    def __init__(self, val):
        self.val = tuple(val)
    def __hash__(self):
        if not hasattr(self, '_hash'):
            self._hash = reduce(operator.xor, self.val)
        return self._hash
    def __cmp__(self, other):
        return cmp(self.val, other.val)
    def __repr__(self):
        oid_str = '.'.join(map(str, self.val))
        if oid_str in OIDS:
            oid_str = '%s' % OIDS[oid_str]
        return oid_str
    def _encode(self, io):
        elts = self.val
        bytes = []
        bytes.append(40 * elts[0] + elts[1])
        for elt in elts[2:]:
            if elt < 0x7F:
                bytes.append(elt)
            else:
                temp = []
                div = rem = elt
                while div:
                    div, rem = divmod(div, 128)
                    temp.append(rem)
                temp.reverse()
                head = map(lambda x:x | 0x80, temp[:-1])
                bytes = bytes + head + temp[-1:]
        io.write(chr(OBJECT_IDENTIFIER) + unparseLength(len(bytes))
                 + string.join(map(chr, bytes), ''))

class ASN1Parser:
    # Keeps some state around between method invocations, which
    # simplifies programming
    #
    # This code can safely raise EOFError inside methods, which will
    # be caught by parse and raise ValueError, "unexpected end of input"

    def __init__(self, io, offset=0):
        self.io = io
        self._buf = io.getvalue()
        # all these instance variables store information about the
        # more recently read tag  
        self.tag = None
        self.id = None
        self.length = 0
        self.offset = offset
        self.indefinite = 0 #是否非定长
        self.constructed = 0 #是否结构化
        self.__getTag()
        self.__getLength()
    
    def ord(self, c):
        try:
            return ord(c)
        except Exception, e:
            raise ValueError('ord error: %s' % e)
    
    def __getTag(self):
        c = self.io.read(1)
        if c == '':
            raise ValueError('getTag error: c == empty')
        tag = self.ord(c)
        self.id = tag
        self.cls = self.id & CLASS_MASK
        self.tag = tag & TYPE_MASK

        if tag & 0x20: #判断第6位为1是结构化定长，0为简单定长
            self.constructed = 1

        if self.tag == TYPE_MASK: #若5-1位都是1，则是high tag
            # high-tag-number
            """第二个和以后的字节给出标签值，基于128，最高位在先，
            以便使用尽可能少的数字，除了最后一个字节以外，每个字节的bit 8都置为1。最后一个字节的为0。"""
            tag = 0
            while 1:
                c = self.io.read(1)
                c = self.ord(c)
                tag = (tag << 7) | (c & 0x7F)
                if c & 0x80 == 0x00: # 最后一个字节的为0
                    break
            self.tag = tag
        return self.tag

    def __getLength(self):
        oct1 = self.io.read(1)
        if oct1 == '':
            raise ValueError('getLength error: c == empty')
        oct1 = self.ord(oct1)
        if oct1 == 0x80: # 非定长
            self.length = 0
            self.indefinite = 1
            print '%x' % self.tag
            print 'indefinite'
        elif oct1 & 0x80: #long form
            # lower bits indicate number of octets to represent length
            oct1 = oct1 & 0x7F
            buf = self.io.read(oct1)
            lens = map(ord, buf)
            length = 0
            for l in lens:
                length = (length << 8) | l
            self.length = length
        else: #short form
            self.length = oct1 & 0x7F
        return self.length

    def getBody(self):
        try:
            buf = self.io.read(self.length)
        except OverflowError, e:
            raise ValueError('getBody error: %s' % e)
        if len(buf) != self.length:
            raise ValueError('getBody error: buffer length %d != asn1.length: %d, buf: %s' \
                             % (len(buf), self.length, buf))
        return buf
    
    def check_encapsulate(self, length):
        return self.cls in (ClassType.Universal, ClassType.ContextSpecific) \
            and (self.tag >=0 and self.tag <= 0x31) \
            and self.indefinite == 0 and self.length == length - self.io.tell()
    
    def parse(self):
#        try:
#            tag = self.getTag()
#            length = self.getLength()
#        except EOFError:
#            raise ValueError("unexpected end of encoded data")
        return self._parse()

    def _parse(self):
        if self.cls == ClassType.Universal: #universal 00
            # class is universal 
            try:
                method = self.__dispatch[self.tag]
            except KeyError:
#                if self.tag == 0:
#                    return None
                self.val = self.parseUnknown()
            else:
                self.val = method(self)
        elif self.cls == ClassType.ContextSpecific: #context-specific 10
            # class is context-specific
            self.val = self.parseContextSpecific()
        else: # application 01 and private not supported. 11
            raise ValueError("invalid or unsupported tag: %X (id %X)" % (self.tag,
                                                              self.id
                                                              & 0xC0))
        if isinstance(self.val, ASN1Object):
            self.val.offset = self.offset
            self.val.id = self.id
            self.val.tag = self.tag
            self.val.length = self.length
        return self.val

    def parseBoolean(self):
        buf = self.getBody()
        if len(buf) != 1:
            raise ValueError('parseBoolean error: Boolean length must be 1, but got: %d' % len(buf))
        b = ord(buf)
        return Boolean(b)

    def parseUnknown(self):
        print 'unknown tag: 0x%X, length: %d' % (self.tag, self.length)
#        if self.tag == 0 and self.length == 0:
        buf = self.getBody()
        print repr(buf)
        return buf

    def parseInteger(self):
        buf = self.getBody()
        if len(buf) == 0:
           raise ValueError('parseInter error')
        return Integer(getInteger(buf))

    def parseZero(self):
        # XXX why is this zero? what does it all mean?
        if self.id & 0x80:
            # this hack retrieves the version number from x509
            return self.length

    def parseUTCTime(self):
        return UTCTime(self.getBody())

    def parseBitString(self):
        # XXX this isn't right yet
        buf = self.getBody()
        if not buf:
            raise ValueError('parseBitString error: buf could not be empty str.')
        unused = self.ord(buf[0])
#        if unused != 0:
#            print "XXX", unused, "unused bits"
        if self.length == 2:
            bits = ord(buf[1]) >> unused
            bytes = [bits]
        else:
            bytes = map(ord, buf[1:-1])
            bits = 0
            for b in bytes:
                bits = bits << 8 | b
            bits = (bits << (8 - unused)) | (ord(buf[-1]) >> unused)
            bytes.append(ord(buf[-1]) >> unused)
        bs = BitString(bytes, buf)
        return bs

    def parsePrintableString(self):
        return PrintString(self.getBody())
    
    def parseBMPString(self):
        return BMPString(self.getBody())
    
    def parseUTF8String(self):
        return UTF8String(self.getBody())

    def parseOctetString(self):
        return OctetString(self.getBody())

    def parseSet(self):
        s = Set()
        return self._parse_seq(s)

    def parseSequence(self):
        seq = Sequence()
        return self._parse_seq(seq)
    
    def parseContextSpecific(self):
        if self.length == 0 and not self.indefinite:
            raise ValueError("don't know how to handle CHOICE with indefinite length")
        con = Contextual(self.constructed)
        if self.constructed:
            return self._parse_seq(con)
        else:
            con.data.append(self.getBody())
            return con
#        return self._parse_seq(con)
#        base = self.io.tell()
#        buf = self.getBody()
#        newIO = StringIO(buf)
#        while 1:
#            p = base + newIO.tell()
#            if p == base + self.length:
#                break
#            try:
#                obj = ASN1Parser(newIO, p).parse()
#            except ValueError:
#                con.unknow = True
#                con.val = buf
#                return con
#            con.append(obj)
#        con.unknow = False
#        return con
    
    def _parse_seq(self, seq):
#        buf = self.getBody()
        base = self.io.tell()
        while 1:
            p = self.io.tell()
            if p == base + self.length:
                break
            obj = ASN1Parser(self.io, p).parse()
            seq.append(obj)
        return seq
    
    def parseObjectIdentifier(self):
        buf = self.getBody()
        o1 = ord(buf[0])
#        try:
#            o1 = self.ord(buf[0])
#        except IndexError:
#            raise EOFError
        x = o1 / 40
        y = o1 % 40
        if x > 2:
            y = y + (x - 2) * 40
            x = 2
        oid = [x, y]
        
        num = None
        for oct in map(ord, buf[1:]):
            if oct & 0x80:
                if num:
                    num = (num << 7L) | (oct & 0x7F)
                else:
                    num = long(oct & 0x7f)
            else:
                if num:
                    final = (num << 7L) | oct
                    # Is there a better way to do this?
                    # Should I just make it long all the time?
                    try:
                        oid.append(int(final))
                    except OverflowError:
                        oid.append(final)
                    num = None
                else:
                    oid.append(oct)
        return OID(oid)

    def parseNull(self):
        self.getBody()
        return Null(None)

    __dispatch = {ASN1_TYPES['SEQUENCE']: parseSequence,
                  ASN1_TYPES['INTEGER']: parseInteger,
                  ASN1_TYPES['SET']: parseSet,
                  ASN1_TYPES['UTCTIME']: parseUTCTime,
                  ASN1_TYPES['BIT_STRING']: parseBitString,
                  ASN1_TYPES['OCTET_STRING']: parseOctetString,
                  ASN1_TYPES['IA5String']: parsePrintableString,
                  ASN1_TYPES['T61String']: parsePrintableString,
                  ASN1_TYPES['PrintableString']: parsePrintableString,
                  ASN1_TYPES['VisibleString']: parsePrintableString,
                  ASN1_TYPES['BMPString']: parseBMPString,
                  ASN1_TYPES['UTF8String']: parseUTF8String,
                  ASN1_TYPES['SET']: parseSet,
                  ASN1_TYPES['OBJECT_IDENTIFIER']: parseObjectIdentifier,
                  ASN1_TYPES['NULL']: parseNull,
                  ASN1_TYPES['BOOLEAN']: parseBoolean,
#                  ASN1_TYPES['ZERO']: parseZero,
                  }

def _hex_str(bytes):
    if not isinstance(bytes, list):
        bytes = map(ord, bytes)
    f = lambda x:len(x)==1 and '0%s' % x or x
    return ' '.join([f('%X' % b) for b in bytes])
    
def getInteger(buf):
    bytes = map(ord, buf)
    if bytes[0] & 0x80:
        sign = -1
    else:
        sign = 1
    value = long(bytes[0] & 0x7F)
    for byte in bytes[1:]:
        value = (value << 8) | byte
    if sign == 1:
        return value
    else:
        return -value

def unparseContextual(tag, enc, constructed=1):
    return chr((constructed and 0x40) | 0x80 | tag) \
           + unparseLength(len(enc)) + enc

def unparseSequence(encObjs, constructed=1):
    buf = string.join(encObjs, '')
    return chr(constructed and 0x20 | ASN1_TYPES['SEQUENCE'] or ASN1_TYPES['SEQUENCE']) \
           + unparseLength(len(buf)) + buf

def unparseNull():
    return '\005\000'

def unparseSet(encObjs, constructed=1):
    # XXX actually, you need to sort the elements in the set before encoding
    buf = string.join(encObjs, '')
    return chr(constructed and 0x20 |SET or SET) \
           + unparseLength(len(buf)) + buf

def unparseBitString(str):
    unused = 0
    return chr(BIT_STRING) + unparseLength(len(str) + 1) + chr(unused) + str

def unparsePrintableString(str):
    unused = 0
    return chr(PrintableString) + unparseLength(len(str)) + str

def unparseOctetString(str):
    unused = 0
    return chr(OCTET_STRING) + unparseLength(len(str)) + str

def unparseInteger(num):
    if num < 0:
        sign = -1
        num = -num
    else:
        sign = 1
    if num == 0:
        bytes = [0]
    else:
        bytes = []
        div = num
        rem = 0
        while div:
            div, rem = divmod(div, 256)
            bytes.append(int(rem))
        last = bytes[-1]
        if last & 0x80:
            bytes.append(0)
    if sign == -1:
        bytes[-1] = bytes[-1] | 0x80
    bytes.reverse()
    return chr(ASN1_TYPES['INTEGER']) + unparseLength(len(bytes)) \
           + string.join(map(chr, bytes), '')
    
def unparseLength(length):
    if length <= 127:
        return chr(length)
    bytes = []
    div = length
    while div:
        div, rem = divmod(div, 256)
        bytes.append(rem)
    bytes.reverse()
    return chr(0x80|len(bytes)) + string.join(map(chr, bytes), '')

def convertOctetsToInt(buf):
    # XXX this really is a kludge
    l = len(buf)
    if l <= 4:
        return struct.unpack(">l", chr(0) * (4 - l) + buf)[0]
    else:
        val = 0L
        for byte in map(ord, buf):
            val = (val << 8) | byte
        return val

def parseCfg(io):
    """Parse dumpasn1 Object Identifier configuration file

    Returns a dictionary mapping OID objects to human-readable
    descriptions. 

    The configuration file is available at the following URL:
    http://www.cs.auckland.ac.nz/~pgut001/dumpasn1.cfg
    (Last verified Apr 10, 2000.)
    """
    oids = {}
    oid = None
    while 1:
        line = io.readline()
        if line == '':
            break
        line = string.strip(line)
        if not line or line[0] == '#':
            continue
        try:
            name, val = map(string.strip, string.split(line, '=', 1))
        except ValueError:
            name = line
            val = None

        if name == 'OID':
            if oid:
                oids[oid] = dict
            bytes = string.join(map(chr,
                                    map(eval,
                                        map(lambda s:"0x"+s,
                                            string.split(val)))), '')
            oid = parse(bytes)
            key = oid
            dict = {}
        else:
            dict[name] = val
    if oid:
        oids[oid] = dict
    return oids

def parse(buf):
    return ASN1Parser(StringIO(buf)).parse()

if __name__ == '__main__':
    data1 = open('./qq_signeddata.cer', 'rb').read()
    cers = parse(data1)
    display(cers)
    
